/**
 * Copyright 2010 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package saig.engine.editors;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JComponent;
import javax.swing.JViewport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import saig.engine.Level;
import saig.engine.models.Flag;
import saig.engine.models.Model;
import saig.engine.models.Player;
import saig.engine.models.Spawn;
import saig.engine.models.enums.ModelType;


/**
 * Level Pane, used for editing a level in the map editor.
 * @author Adam
 * @version $Id$
 * 
 * Created on Jul 6, 2010 at 10:01:49 PM 
 */
public class LevelPane extends JComponent implements MouseListener,	MouseMotionListener {
	/**
	 * Serial Version UID.
	 */
	private static final long serialVersionUID = -3383413140626992276L;
	/**
	 * The current button state.
	 */
	private int buttonState = 0;
	/**
	 * Contains the level information.
	 */
	private Level level;
	/**
	 * Logger.
	 */
	final Logger logger = LoggerFactory.getLogger(LevelPane.class);
	/**
	 * The map editor used to get information on the selected tile.
	 */
	private MapEditor mapEditor;
	/**
	 * Stores the final x position for the mouse.
	 */
	private int mEndX;
	/**
	 * Stores the final y position for the mouse.
	 */
	private int mEndY;
	/**
	 * Stores the initial x position for the mouse.
	 */
	private int mStartX;
	/**
	 * Stores the initial y position for the mouse.
	 */
	private int mStartY;
	/**
	 * The view port.
	 */
	private JViewport viewport;
	/**
	 * Constructs a Level Pane for editing a level.
	 * @param level The level to edit.
	 * @param selectorPane The tile pane.
	 */
	public LevelPane(final Level level, final MapEditor mapEditor) {
		this.mapEditor = mapEditor;
		this.level = level;
		setPreferredSize(new Dimension(level.getWidth() * level.getTileWidth(),
				level.getHeight() * level.getTileHeight()));
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
	}
	/**
	 * Renders the current level at a given view position.
	 * @param g The graphcis object to render to.
	 * @param viewPosition The view position.
	 * @param viewSize The size of the view.
	 */
	private void doRender(Graphics g, final JViewport viewport) {
		final Graphics2D g2 = (Graphics2D) g;
		final double x = viewport.getViewPosition().getX();
		final double y = viewport.getViewPosition().getY();
		final double width = viewport.getSize().getWidth();
		final double height = viewport.getSize().getHeight();
		final int startX = (int) x / level.getTileWidth();
		final int endX = (int) Math.min(Math.ceil((x + width) / level.getTileWidth()), level.getWidth());
		final int startY = (int) y / level.getTileHeight();
		final int endY = (int) Math.min(Math.ceil((y + height) / level.getTileHeight()), level.getHeight());
		for (int xpos = startX; xpos < endX; xpos++) {
			for (int ypos = startY; ypos < endY; ypos++) {
				if (level.getTile(xpos, ypos) != null) {
					int drawX = xpos * level.getTileWidth();
					int drawY = ypos * level.getTileHeight();
					g.drawImage(level.getTile(xpos, ypos).getImage(), drawX, drawY, null);
				}
			}
		}
		final int maxX = (int) (x + width); final int maxY = (int) (y + height);
		for (int tx = (int) x; tx <= maxX; tx+= level.getRegionSize()) {
			for (Spawn spawn : level.getSpawns(tx)) {
				// Don't draw the spawn model if it is not in view.
				if (spawn.getX() >= x && spawn.getY() >= y && spawn.getX() <= maxX && spawn.getY() <= maxY) {
					Model model = spawn.getModel(null);
					// model.setX(drawX); model.setY(drawY);
					model.setDirection(-1);
					model.doRender(g, 0, 0);
				}
			}
		}
		if (level.getStart() != null && level.getStart().getX() >= x && level.getStart().getY() >= y
				&& level.getStart().getX() <= maxX && level.getStart().getY() <= maxY) {
			Player player = new Player(null);
			player.setX((float) level.getStart().getX());
			player.setY((float) level.getStart().getY());
			player.setDirection(1);
			player.doRender(g, 0, 0);
		}
		if (level.getEnd() != null && level.getEnd().getX() >= x && level.getEnd().getY() >= y
				&& level.getEnd().getX() <= maxX && level.getEnd().getY() <= maxY) {
			Flag flag = new Flag(null);
			flag.setX((float) level.getEnd().getX());
			flag.setY((float) level.getEnd().getY());
			flag.doRender(g, 0, 0);
		}
		if (level.isShowGrid()) {
			g.setColor(Color.gray);
			for(int xpos=0; xpos <= level.getPixelWidth(); xpos += level.getTileWidth()) {
				g.drawLine(xpos, 0, xpos, level.getPixelHeight());
			}
			for(int ypos=0; ypos <= level.getPixelHeight(); ypos += level.getTileHeight()) {
				g.drawLine(0, ypos, level.getPixelWidth(), ypos);
			}
		}
		if (buttonState == MouseEvent.BUTTON1) {
			if (mapEditor.getActiveSelectorPane() == SelectorPane.TILE_PANE) {
				g.setColor(Color.BLUE);
				g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
				g2.fillRect(mStartX, mStartY, mEndX - mStartX, mEndY - mStartY);
				g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
				g2.drawRect(mStartX, mStartY, mEndX - mStartX, mEndY - mStartY);
			} else {
				g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
				Model model = mapEditor.getSelectedModel();
				model.setX((float) mEndX);
				int modelY = mEndY / level.getTileHeight() * level.getTileHeight() + level.getTileHeight();
				model.setY(modelY);
				if (model.getType() != ModelType.PLAYER) {
					model.setDirection(-1);
				}
				model.doRender(g, 0, 0);
				g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
			}
		} else if (buttonState == MouseEvent.BUTTON3) {
			g.setColor(Color.BLUE);
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
			g2.fillRect(mEndX - level.getTileWidth(), mEndY - level.getTileHeight(),
					level.getTileWidth() * 2, level.getTileHeight() * 2);
			g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
			g2.drawRect(mEndX - level.getTileWidth(),  mEndY - level.getTileHeight(),
					level.getTileWidth() * 2, level.getTileHeight() * 2);
		}
	}
	/**
	 * Returns level.
	 * @return the level.
	 */
	public Level getLevel() {
		return level;
	}
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseClicked(final MouseEvent e) {
		
	}
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseDragged(final MouseEvent e) {
		// logger.debug("Dragged [{}, {}] [buttonState={}]", new Object[] {e.getX(), e.getY(), buttonState});
		
			mEndX = e.getX();
			mEndY = e.getY();
			//setPosition(e.getX(), e.getY());
		repaint();
	}
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseEntered(final MouseEvent e) { }
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseExited(final MouseEvent e) { }
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseMoved(final MouseEvent e) { }
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
	 */
	@Override
	public void mousePressed(final MouseEvent e) {
		logger.debug("Button Pressed [{}], Previous State [{}]", e.getButton(), buttonState);
		buttonState = e.getButton();
		mStartX = mEndX = e.getX();
		mStartY = mEndY = e.getY();
	}
	/**
	 * {@inheritDoc}
	 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseReleased(final MouseEvent e) {
		if (buttonState == MouseEvent.BUTTON1) {
			if (e.getX() >= mStartX) {
				mEndX = e.getX();
			} else {
				mEndX = mStartX;
				mStartX = e.getX();
			}
			if (e.getY() >= mStartY) {
				mEndY = e.getY();
			} else {
				mEndY = mStartY;
				mStartY = e.getX();
			}
			if (mapEditor.getActiveSelectorPane() == SelectorPane.TILE_PANE) {
				for (int y = mStartY; y <= mEndY; y+= 16) {
					for (int x = mStartX; x <= mEndX; x+= 16) {
						setPosition(x, y);
					}
				}
			} else if (mapEditor.getActiveSelectorPane() == SelectorPane.MODEL_PANE) {
				setPosition(e.getX(), e.getY());
			}
		} else if (buttonState == MouseEvent.BUTTON3) {
			level.removeSpawns(e.getX() - level.getTileWidth(), e.getY() - level.getTileHeight(),
					e.getX() + level.getTileWidth(),  e.getY() + level.getTileHeight());
		}
		buttonState = MouseEvent.NOBUTTON;
		repaint();
	}
	/**
	 * {@inheritDoc}
	 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
	 */
	public void paintComponent(final Graphics g) {
		doRender(g, viewport);
		int width = level.getPixelWidth();
		int height = level.getPixelHeight();
		// Create the frame.
		((Graphics2D)g).setStroke(new BasicStroke(2));
		g.setColor(Color.black);
		g.drawLine(0, 0, width, 0);
		g.drawLine(0, 0, 0, height);
		g.drawLine(width, 0, width, height);
		g.drawLine(0, height, width, height);
	}
	/**
	 * Sets level.
	 * @param level the level to set.
	 */
	public void setLevel(Level level) {
		this.level = level;
	}
	/**
	 * Sets the tile/model at the event's X and Y position.
	 * @param eventX The event x position.
	 * @param eventY The event y position.
	 */
	private void setPosition(final int eventX, final int eventY) {
		final int x = eventX / level.getTileWidth();
		final int y = eventY / level.getTileHeight();
		if (mapEditor.getActiveSelectorPane() == SelectorPane.TILE_PANE) {
			logger.debug("Setting tile to {} at position [{}, {}]",
					new Object[] {mapEditor.getSelectedTile(), x, y});
			level.setTile(x, y, mapEditor.getSelectedTile());
		} else if (mapEditor.getActiveSelectorPane() == SelectorPane.MODEL_PANE) {
			final int yloc = y * level.getTileHeight() + level.getTileHeight() - 1;
			logger.debug("Setting model to {} at position [{}, {}]",
					new Object[] {mapEditor.getSelectedModel(), eventX, eventY});
			if (mapEditor.getSelectedModel().getType() == ModelType.PLAYER) {
				level.setStart(new Point(eventX, yloc));
			} else if (mapEditor.getSelectedModel().getType() == ModelType.FLAG) {
				level.setEnd(new Point(eventX, yloc));
			} else {
				level.setSpawn(new Spawn(mapEditor.getSelectedModel(), eventX, yloc));
			}
		} else {
			logger.error("Unable to determine the selected tile/model");
		}
	}
	/**
	 * Sets viewport.
	 * @param viewport the viewport to set.
	 */
	public void setViewport(final JViewport viewport) {
		this.viewport = viewport;
	}
}
